/*
 * Copyright (c) 2012-2013 Open Source Community - <http://www.peerfact.org>
 * Copyright (c) 2011-2012 University of Paderborn - UPB
 * Copyright (c) 2005-2011 KOM - Multimedia Communications Lab
 *
 * This file is part of PeerfactSim.KOM.
 * 
 * PeerfactSim.KOM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 * 
 * PeerfactSim.KOM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with PeerfactSim.KOM.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package org.peerfact.impl.overlay.dht.pastry.parallel.components;

import java.util.List;

import org.peerfact.api.common.OperationCallback;
import org.peerfact.api.overlay.dht.DHTObject;
import org.peerfact.api.transport.TransLayer;
import org.peerfact.impl.overlay.dht.pastry.base.components.PastryContact;
import org.peerfact.impl.overlay.dht.pastry.base.components.PastryID;
import org.peerfact.impl.overlay.dht.pastry.base.components.PastryKey;
import org.peerfact.impl.overlay.dht.pastry.base.nodestate.RouteSet;
import org.peerfact.impl.overlay.dht.pastry.parallel.operations.LookupOperation;
import org.peerfact.impl.overlay.dht.pastry.parallel.operations.ValueLookupOperation;
import org.peerfact.impl.simengine.Simulator;

/**
 * This class is used to represent nodes of the pastry overlay.
 * 
 * @author Julius Rueckert <peerfact@kom.tu-darmstadt.de>
 * 
 * @version 05/06/2011
 */
public class PastryNode extends
		org.peerfact.impl.overlay.dht.pastry.base.components.PastryNode {

	/**
	 * number of parallel lookups. 1 means only one lookup will be started.
	 */
	private int parallelLookups = 1;

	protected PastryNode(TransLayer translayer) {
		super(translayer);

		routingTable = new PastryRoutingTable(this);
	}

	/*
	 * new constructor by Martin Wiegand rowBucketsize is true if amount of
	 * redundance should depend on current routing table row. exponentialShrink
	 * is true if redundance shrinks exponentially diversityFirst is true if
	 * redundant entries are selected by diversity parallelLookups = amount of
	 * lookups iterative true if iterative routing should be used. resplit is
	 * true if lookups are split up again.
	 */
	protected PastryNode(TransLayer translayer, int maxRedundance,
			boolean rowBucketsize, boolean exponentialShrink,
			boolean diversityFirst, boolean iterative, int parallelLookups,
			boolean resplit) {
		super(translayer);

		this.parallelLookups = parallelLookups;

		// Register the message handler at the transport layer
		msgHandler = new PastryMessageHandler(this, resplit,
				iterative);
		getTransLayer().addTransMsgListener(msgHandler, getPort());

		routingTable = new PastryRoutingTable(this, maxRedundance,
				rowBucketsize, exponentialShrink, diversityFirst);
	}

	// new getter by Martin Wiegand
	public int getParallelLookups() {
		return this.parallelLookups;
	}

	// new method to get parallelLookups many entries for parallel lookups
	public RouteSet getNextHops(PastryID id) {
		RouteSet r = new RouteSet();
		RouteSet temp = new RouteSet();

		// if node is in leafset range route to it directly
		if (leafSet.isInRange(id)) {
			r.insertEntry(leafSet.getNumericallyClosestContact(id));
			return r;
		}

		// otherwise get entries from the leafSet
		int i;
		for (i = 0; i < parallelLookups; i++) {
			PastryContact c = leafSet.getNumericallyClosestContact(id);
			if (c != null) {
				temp.insertEntry(c);
				leafSet.remove(c);
			}
		}
		for (PastryContact c : temp) {
			leafSet.put(c);
			r.insertEntry(c);
		}
		temp = new RouteSet();

		// the neighborhoodSet
		for (i = 0; i < parallelLookups; i++) {
			PastryContact c = neighborhoodSet.getNumericallyClosestContact(id);
			if (c != null) {
				temp.insertEntry(c);
				neighborhoodSet.remove(c);
			}
		}
		for (PastryContact c : temp) {
			neighborhoodSet.put(c);
			r.insertEntry(c);
		}
		temp = new RouteSet();

		// and the routing table
		for (i = 0; i < parallelLookups; i++) {
			PastryContact c = ((PastryRoutingTable) routingTable)
					.getNumericallyClosest(id);
			if (c != null) {
				temp.insertEntry(c);
				((PastryRoutingTable) routingTable).remove(c);
			}
		}
		for (PastryContact c : temp) {
			((PastryRoutingTable) routingTable).insertContact(c);
			r.insertEntry(c);
		}
		temp = new RouteSet();

		// and return the combined routing entries.
		return r;
	}

	@Override
	public int valueLookup(PastryKey key, OperationCallback<DHTObject> callback) {
		// Do not look the value up if the node is absent
		if (getPeerStatus() != PeerStatus.PRESENT) {
			return -1;
		}
		// Inform the monitors about an initiated query
		Simulator.getMonitor()
				.dhtLookupInitiated(getLocalOverlayContact(), key);

		ValueLookupOperation op = new ValueLookupOperation(this, key
				.getCorrespondingId(), callback);
		op.scheduleImmediately();
		return op.getOperationID();
	}

	@Override
	public int nodeLookup(PastryKey key,
			OperationCallback<List<PastryContact>> callback,
			boolean returnSingleNode) {

		// Do not lookup if the node is absent
		if (getPeerStatus() != PeerStatus.PRESENT) {
			return -1;
		}

		// Notify Analyzers
		Simulator.getMonitor()
				.dhtLookupInitiated(getLocalOverlayContact(), key);
		// If the key is a pastry key, schedule a new lookup
		LookupOperation op = new LookupOperation(this,
				key.getCorrespondingId(), callback);
		op.scheduleImmediately();

		return op.getOperationID();
	}

	// new method by Martin Wiegand to check if a node contains an operation
	// without removing it from the registered operations
	public boolean containsOperation(Integer operationId) {
		return this.registeredOperations.containsKey(operationId);
	}

	// method used by the analyzer to call the routing tables method
	public double getBucketStateLvl(int level) {
		return ((PastryRoutingTable) routingTable).getBucketStateLevel(level);
	}

	public static int getNumOfRows() {
		return PastryRoutingTable.getNumOfRows();
	}

	public static int getNumOfColumns() {
		return PastryRoutingTable.getNumOfColumns();
	}

}
